#include "jstypes.h"
#include "jsparse.h"
#include "y.tab.h"
#include "lex.yy.h"

void
print_node (JSNode *node, char *pref)
{
	char *pr = g_strconcat(pref, "\t", NULL);
	JSNode *iter;
	gchar *in;

	if (node == NULL)
		return;

	printf ("%s%d\n",pref,node->pn_type);
	switch (node->pn_arity)
	{
		case PN_UNARY:
			printf ("%sUNARY\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
			case TOK_SEMI:
				print_node (node->pn_u.unary.kid, pr);
				break;
			}
			break;
		case PN_LIST:
			printf ("%sLIST\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
			case TOK_VAR:
				for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
				{
					g_assert (iter->pn_type == TOK_NAME);

					const gchar *name = get_node_name(iter);
					break;
				}
			case TOK_LP:
				print_node (node->pn_u.list.head, pr);
				int k = 0;
				for (iter = ((JSNode*)node->pn_u.list.head)->pn_next; iter != NULL; iter = iter->pn_next, k++)
				{
					print_node (iter, pr);
				}
				break;
			case TOK_RC:
				{
				print_node (node->pn_u.list.head, pr);
				int k = 0;
				for (iter = ((JSNode*)node->pn_u.list.head)->pn_next; iter != NULL; iter = iter->pn_next, k++)
				{
					print_node (iter, pr);
				}
				}
				break;
			case TOK_LC:
				for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
				{
					print_node (iter, pr);
				}
				break;
			case TOK_NEW:
				///NOT COMPLETE
				print_node (node->pn_u.list.head, pr);
				for (iter = ((JSNode*)node->pn_u.list.head)->pn_next; iter != NULL; iter = iter->pn_next)
				{
					print_node (iter, pr);
				}
				break;
			}
			break;
		case PN_BINARY:
			printf ("%sBINARY\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
				case TOK_ASSIGN:
					///NOT COMPLETE
					print_node (node->pn_u.binary.left, pr);
					print_node (node->pn_u.binary.right, pr);

					break;
			}
			break;
		case PN_FUNC:
			printf ("%sFUNC\n", pref);
			print_node (node->pn_u.func.body, pr);
			break;
		case PN_NAME:
//TODO			printf ("%sNAME\n%s\n", pref, node->pn_u.name.name);
			printf ("%sNAME\n", pref);
			print_node (node->pn_u.name.expr, pr);
/*			JSAtom * atom = node->pn_atom;
			*code = js_AtomToPrintableString(context, node->pn_atom);			*/
			break;
		case PN_NULLARY:
			printf ("%sNULL\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
				case TOK_STRING:
//					*code = g_strconcat ("\"", js_AtomToPrintableString(context, node->pn_atom), "\"", NULL);			
					break;
				case TOK_NUMBER:
//					*code = g_new (char, 100);
//					sprintf (*code, "%lf", node->pn_u.dval);
					break;
			}
			break;
		case PN_TERNARY:
			printf ("%sTERNARY\n", pref);
/*			print (node->pn_u.ternary.kid1, pr, &in);
			print (node->pn_u.ternary.kid2, pr, &in);			
			print (node->pn_u.ternary.kid3, pr, &in);			*/
			break;
		
	}
	g_free (pr);
	return;
}

void
print_context (Context *my_cx, const gchar *str)
{
	GList *i;
	printf ("%s%d-%d\n", str, my_cx->bline, my_cx->eline);
	for (i = my_cx->local_var; i; i = g_list_next (i))
	{
		Var *t = (Var *)i->data;
		if (!t->name)
			continue;
		printf ("%s%s\n", str, t->name);
	}
	for (i = g_list_last (my_cx->childs); i; i = g_list_previous (i))
	{
		Context *t = (Context *)i->data;
		print_context (t, g_strdup_printf ("%s%s","\t", str));
	}
}

GList*
get_member_from_rc (JSParseNode* node)
{
	GList *ret = NULL;
	JSParseNode* iter;
	if (node->pn_type != TOK_RC)
		return NULL;
	for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
	{
		const gchar* name = get_node_name (iter->pn_u.binary.left);
//		puts (name);
		if (!name)
			g_assert_not_reached ();
		else
			ret = g_list_append (ret, g_strdup (name));
	}
	return ret;
}

JSParseNode*
get_member_rc (JSParseNode* node, const gchar *mname)
{
	GList *ret = NULL;
	JSParseNode* iter;
	if (node->pn_type != TOK_RC)
		return NULL;
	for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
	{
		const gchar* name = get_node_name (iter->pn_u.binary.left);
//		puts (name);
		if (!name)
		{
			g_assert_not_reached ();
			continue;
		}
		if (strcmp (mname, name) != 0)
			continue;
		return iter->pn_u.binary.right;
	}
	return NULL;
}

GList*
get_lines_missed_semicolon ()
{
	return line_missed_semicolon;
}

JSParseNode*
parse_file (const gchar *name)
{
	JSParseNode *ret = g_new (JSParseNode, 1);
	FILE *f = fopen (name, "r");

	line_missed_semicolon = NULL;

	yyset_lineno (1);
	YY_BUFFER_STATE b = yy_create_buffer (f,10000);
	yy_switch_to_buffer (b);

	yyparse ();

	fclose (f);

	yy_delete_buffer (b);
	if (!global)
	{
		g_free (ret);
		return NULL;
	}
	(*ret) = *global;

	return ret;
}

void
jsnode_free (JSNode *self)
{
	if (!self)
		return;
	switch (self->pn_arity)
	{
	case PN_FUNC:
		jsnode_free (self->pn_u.func.body);
		jsnode_free (self->pn_u.func.name);
//		void *args;

		break;
	case PN_LIST:
		jsnode_free (self->pn_u.list.head);
		break;
	case PN_TERNARY:
		break;
	case PN_BINARY:
		jsnode_free (self->pn_u.binary.left);
		jsnode_free (self->pn_u.binary.right);
		break;
	case PN_UNARY:
		jsnode_free (self->pn_u.unary.kid);
		break;
	case PN_NAME:
		jsnode_free (self->pn_u.name.expr);
		jsnode_free (self->pn_u.name.name);
		break;
	case PN_NULLARY:
		break;
	}

/*TODO:	if (self->pn_next)
		jsnode_free (self->pn_next);*/
	g_free (self);
}

void
js_context_free (Context *self)
{
	g_list_foreach (self->local_var, g_free, NULL);
	g_list_free (self->local_var);
	g_list_foreach (self->childs, js_context_free, NULL);
	g_list_free (self->childs);
	g_free (self->func_name);
	g_list_free (self->ret_type);
	g_list_free (self->func_arg);
	g_free (self);
}

Context* 
js_context_new (Context *parent)
{
	Context *ret = g_new (Context, 1);

	ret->func_arg = NULL;
	ret->func_name = NULL;
	ret->parent = parent;
	ret->childs = NULL;
	ret->local_var = NULL;
	ret->bline = 0;
	ret->eline = 0;
	ret->ret_type = NULL;

	return ret;
}

const gchar*
get_node_name (JSParseNode *node)
{
	if (!node)
		return NULL;
	if (node->pn_arity == PN_NULLARY)
	{
			return NULL;
//TODO		return js_AtomToPrintableString(cx, node->pn_atom);
	}
	if (node->pn_arity != PN_NAME)
		return NULL;
	switch ((JSTokenType)node->pn_type)
	{
		case TOK_NAME:
			return node->pn_u.name.name;
			break;
		case TOK_DOT:
			return g_strdup_printf ("%s.%s", get_node_name (node->pn_u.name.expr), get_node_name (node->pn_u.name.name));
			break;
		default:
			g_assert_not_reached ();
			break;
	}
	return NULL;
}

Type*
get_node_type (JSParseNode *node, Context *my_cx)
{
	Type *ret;
	const gchar *name;
	gchar *type;
	if (!node)
		return NULL;

	ret = g_new (Type, 1);
	ret->isFuncCall = FALSE;
	switch ((JSParseNodeArity)node->pn_arity)
	{
		case PN_NAME:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_NAME:
						name = get_node_name (node);
						if (!name)
							g_assert_not_reached ();
						else
						{
							JSParseNode *t = find_last_assignment (name, my_cx);
							if (t)
							{
								Type *tname = get_node_type (t, my_cx);
								if (tname)
									return tname;
								else
									ret->name = g_strdup (name);
							}
							else
								ret->name = g_strdup (name);
							return ret;
						}
						break;
					case TOK_DOT:
						name = get_node_name (node);
						if (!name)
							g_assert_not_reached ();
						else
						{
							JSParseNode *t = find_last_assignment (name, my_cx);
							if (t)
							{
								Type *tname = get_node_type (t, my_cx);
								if (tname)
									return tname;
								else
									ret->name = g_strdup (name);
							}
							else
								ret->name = g_strdup (name);
							return ret;
						}
						break;
					default:
						g_assert_not_reached ();
						break;
				}
				break;
		case PN_NULLARY:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_STRING:
//							puts ("string");
							ret->name = g_strdup ("String");
							return ret;
							break;
					case TOK_NUMBER:
///							puts ("float");
							ret->name = g_strdup ("Number");
							return ret;
							break;
					case TOK_PRIMARY:
							switch (node->pn_op)
							{
								case JSOP_FALSE:
								case JSOP_TRUE:
									ret->name = g_strdup ("Boolean");
									return ret;
									break;
								case JSOP_NULL:
									ret->name = g_strdup ("null");
									return ret;
									break;
								case JSOP_THIS:
									ret->name = g_strdup ("Object");
									return ret;
									break;
								default:
									printf ("%d\n", node->pn_op);
									g_assert_not_reached ();
									break;
							}
							break;
					default:
							printf ("%d\n", node->pn_type);
							g_assert_not_reached ();
							break;
				}
				break;
		case PN_LIST:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_NEW:
							name = get_node_name (node->pn_u.list.head);
							if (!name)
								g_assert_not_reached ();
							else
							{
//								puts (name);
								ret->name = g_strdup (name);
								return ret;
							}
							break;
					case TOK_LP:
							name = get_node_name (node->pn_u.list.head);
							if (!name)
								g_assert_not_reached ();
							else
							{
//								printf ("call %s\n",name);
								ret->isFuncCall = TRUE;
								ret->name = g_strdup (name);
								return ret;								
							}
							break;
					case TOK_PLUS:
/*TODO						if (node->pn_extra == PNX_STRCAT)
							return g_strdup ("String");*/
						ret->name = g_strdup ("Number");
						return ret;
						break;
					case TOK_RC:
//TODO:
							break;
					default:
							printf ("%d\n", node->pn_type);
							g_assert_not_reached ();
							break;
				}
				break;
		case PN_FUNC:
				{
/*				JSObject * object = ATOM_TO_OBJECT(node->pn_u.func.funAtom);
				JSFunction * function = (JSFunction *) JS_GetPrivate(cx, object);

				if (function->atom) {
					return g_strdup (js_AtomToPrintableString(cx, function->atom));
				}
				else*/
					ret->name = g_strdup ("Function");
					return ret;
				}
				break;
		case PN_BINARY:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_MINUS:
					case TOK_PLUS:
/*TODO						if (node->pn_extra == PNX_STRCAT)
							return g_strdup ("String");*/
						ret->name = g_strdup ("Number");
						return ret;
						break;
					default:
						printf ("%d\n", node->pn_type);
						g_assert_not_reached ();
						break;
				}
				break;
		case PN_UNARY:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_RP:
						return get_node_type (node->pn_u.unary.kid, my_cx);
						break;
					case TOK_UNARYOP:
						return get_node_type (node->pn_u.unary.kid, my_cx);
						break;
					default:
						printf ("%d\n", node->pn_type);
						g_assert_not_reached ();
						break;
				}
				break;
		case PN_TERNARY:
				switch ((JSTokenType)node->pn_type)
				{
/*TODO					case TOK_HOOK:
						return get_node_type (node->pn_u.ternary.kid2, my_cx);
						break;*/
					default:
						printf ("%d\n", node->pn_type);
						g_assert_not_reached ();
						break;
				}
				break;
		default:
				printf ("%d\n", node->pn_type);
				g_assert_not_reached ();
				break;
	}
	return NULL;
}

void
interpretator (JSParseNode *node, Context *my_cx, GList **calls)
{
	JSParseNode *iter;

	if (node == NULL)
		return;
	switch ((JSParseNodeArity)node->pn_arity)
	{
		case PN_FUNC:
				{
					Var *tvar = g_new (Var, 1);
					tvar->name = NULL;
					tvar->line = node->pn_pos.end;
					tvar->node = node;

					if (node->pn_u.func.name) {
						tvar->name = g_strdup (get_node_name (node->pn_u.func.name));
//puts (tvar->name);
					}
					if (tvar->name)
					{
						my_cx->local_var = g_list_append (my_cx->local_var, tvar);
						Context *t = js_context_new (my_cx);
						t->func_name = g_strdup (tvar->name);
//puts (t->func_name);
						t->bline = node->pn_pos.begin;
						t->eline = node->pn_pos.end;
						interpretator (node->pn_u.func.body, t, calls);
						my_cx->childs = g_list_append (my_cx->childs, t);
//puts (t->func_name);
/*						JSAtom ** params = (JSAtom **)g_malloc (function->nargs * sizeof (JSAtom *));
						gint i;
						for (i = 0; i < function->nargs; i++) {
						    params[i] = NULL;
						}
						JSScope * scope = OBJ_SCOPE(object);
						JSScopeProperty * scope_property;
						for (scope_property = SCOPE_LAST_PROP (scope);
						        scope_property != NULL;
						        scope_property = scope_property->parent) {
						    if (!JSID_IS_ATOM (scope_property->id) || ((uint16)scope_property->shortid) >= function->nargs) {
						    continue;
						    }
						    params[(uint16) scope_property->shortid] = JSID_TO_ATOM (scope_property->id);
						}
						for (i = 0; i < function->nargs; i++) {
						    g_assert (params[i] != NULL);
							const gchar *code = js_AtomToPrintableString(cx, params[i]);
							g_assert (code != NULL);
							t->func_arg = g_list_append (t->func_arg, g_strdup (code));
					    }*/
						JSParseNode *i = (JSParseNode *)node->pn_u.func.args;
						if (i)
						{
							g_assert (i->pn_arity == PN_LIST);
							for (i = (JSParseNode *)i->pn_u.list.head; i; i = (JSParseNode *)i->pn_next)
							{
								g_assert (i->pn_arity == PN_NAME);
								t->func_arg = g_list_append (t->func_arg, get_node_name (i));
							}
						}
					}
				}
				break;
		case PN_LIST:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_LC:
						{
							Context *t = js_context_new (my_cx);
							t->bline = node->pn_pos.begin;
							t->eline = node->pn_pos.end;
							for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
							{
								interpretator (iter, t, calls);
							}
							my_cx->childs = g_list_append (my_cx->childs, t);
						}
						break;
					case TOK_LP:
						{
							const gchar *fname = get_node_name(node->pn_u.list.head);
							if (!fname)
								break;
							FuncCall *t = g_new (FuncCall, 1);
							t->name = g_strdup (fname);
							t->list = ((JSParseNode*)node->pn_u.list.head)->pn_next;
							*calls = g_list_append (*calls, t);
//printf ("call to %s\n", t->name);
						}
						break;
					case TOK_VAR:
						for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
						{
							g_assert (iter->pn_type == TOK_NAME);

							const gchar *name = get_node_name(iter);
							Var *t = g_new (Var, 1);
							t->name = g_strdup (name);
							t->node = iter->pn_u.name.expr;
							t->line = iter->pn_pos.end;
							my_cx->local_var = g_list_append (my_cx->local_var, t);
						}
						break;
					case TOK_RC:
						for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
						{
							//add to Context
						}
						break;
					default:
						break;
				}
		case PN_BINARY:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_ASSIGN:
					///NOT COMPLETE
						{
							const gchar *name = get_node_name (node->pn_u.binary.left);
							Var *t = (Var *)g_new (Var, 1);
							t->name = g_strdup (name);
							t->node = node->pn_u.binary.right;
							t->line = node->pn_pos.end;
							my_cx->local_var = g_list_append (my_cx->local_var, t);
						}
						break;
					case TOK_WHILE:
					case TOK_FOR:
						{
							Context *t = js_context_new (my_cx);
							t->bline = node->pn_pos.begin;
							t->eline = node->pn_pos.end;
							interpretator (node->pn_u.binary.right, t, calls);
							my_cx->childs = g_list_append (my_cx->childs, t);
						}
						break;
					case TOK_DO:
						{
							Context *t = js_context_new (my_cx);
							t->bline = node->pn_pos.begin;
							t->eline = node->pn_pos.end;
							interpretator (node->pn_u.binary.left, t, calls);
							my_cx->childs = g_list_append (my_cx->childs, t);
						}
						break;
					default:
						break;
				}
				break;
		case PN_UNARY:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_SEMI:
						interpretator (node->pn_u.unary.kid, my_cx, calls);
						break;
					case TOK_RETURN:
						{
							Type *type = get_node_type (node->pn_u.unary.kid, my_cx);
							if (type)
							{
								Context *i;
//								puts (type);
								for (i = my_cx; i; i = i->parent)
								{
									if (!i->func_name)
										continue;
									i->ret_type = g_list_append (i->ret_type, type->name);
									break;
								}
							}
						}
						break;
					default:
						break;
				}
				break;
		case PN_TERNARY:
				switch ((JSTokenType)node->pn_type)
				{
					case TOK_IF:
/*TODO						if (node->pn_kid2)
						{
							Context *t = new Context (my_cx);
							t->bline = node->pn_kid2->pn_pos.begin.lineno;
							t->eline = node->pn_kid2->pn_pos.end.lineno;
							interpretator (node->pn_kid2, t, calls);
							my_cx->childs = g_list_append (my_cx->childs, t);
						}
						if (node->pn_kid3)
						{
							Context *t = new Context (my_cx);
							t->bline = node->pn_kid3->pn_pos.begin.lineno;
							t->eline = node->pn_kid3->pn_pos.end.lineno;
							interpretator (node->pn_kid3, t, calls);
							my_cx->childs = g_list_append (my_cx->childs, t);
						}*/
						break;
					default:
						break;
				}
				break;
		case PN_NAME:
		case PN_NULLARY:
			break;
		default:
			printf ("%d\n", node->pn_type);
			g_assert_not_reached ();
			break;
	}
}

JSParseNode*
find_last_assignment (const gchar *name, Context *my_cx)
{
	GList *i;
	for (i = g_list_last (my_cx->local_var); i; i = g_list_previous (i))
	{
		Var *t = (Var *)i->data;
		if (!t->name)
			continue;
		if (strcmp (t->name, name) != 0)
			continue;
		return t->node;
	}
	for (i = g_list_last (my_cx->childs); i; i = g_list_previous (i))
	{
		Context *t = (Context *)i->data;
		JSParseNode *node = find_last_assignment (name, t);
		if (node)
			return node;
	}
	return NULL;
}
